6. Praktyka JavaScriptu

Wyzwania:

  • nabierzesz wprawy w pisaniu javascriptowego kodu,
  • dowiesz się, czym są eventy, obiekty i pętle,
  • zaczniesz tworzyć prostą "aplikację".

Wstęp

W poprzednim module udało nam się wspólnie stworzyć prostą grę! Jest to jednak dopiero początek Twojej nauki JS-a.

W tym module rozpoczniemy projekt, który zajmie nam dwa tygodnie. Mamy Ci do przekazania dużo nowej wiedzy, więc postanowiliśmy rozłożyć pracę w czasie, aby nauka była bardziej efektywna.

Nie obawiaj się rozmiaru tego projektu – będziemy rozwijać go krok po kroku, więc powoli nam powoli przyzwyczajać się do pracy nad nieco większym projektem.

6.1. Kilka słów o zmiennych

Mamy nadzieję, że trochę już rozumiesz pojęcia zmiennych, logiki warunkowej czy funkcji. W tym module będziemy je dalej stosować i pogłębiać naszą znajomość JS-a. Zaczniemy od tematu zmiennych!

Dlaczego potrzebujemy zmiennych?

Jak pamiętasz, zmienne można porównać do "pudełek", w których przechowujemy pewne informacje lub wartości. Możemy później odwoływać się do tych wartości, podając tylko nazwę zmiennej, co znacznie poprawia czytelność i wydajność naszego kodu.

Rozpatrzmy to na przykładzie. Wyobraź sobie, że na stronie mamy element o identyfikatorze (id) równym box, który chcemy modyfikować na różne sposoby w zależności od sytuacji: zmieniać jego tekst, dodawać lub usuwać klasę, i tak dalej. Ponieważ będziemy odwoływać się do tego elementu wielokrotnie w naszym kodzie, możemy zadeklarować dla niego zmienną, a następnie używać jej w poszczególnych operacjach:

// Deklarujemy zmienną

let boxElement = document.getElementById('box');

// Dodajemy klasę do elementu

boxElement.classList.add('active');

// Zmieniamy tekst elementu

boxElement.innerText = "This is new content of our element";

A jak wyglądałby kod, gdybyśmy nie użyli zmiennej?

// Dodajemy klasę do elementu

document.getElementById('box').classList.add('active');

// Zmieniamy tekst elementu

document.getElementById('box').innerText = "This is new content of our element"

Jak widzisz, różnica w ilości i czytelności kodu jest znaczna! Przy rozbudowanych aplikacjach, liczących wiele tysięcy linii kodu, potrzeba używania zmiennych staje się jeszcze bardziej oczywista.

Rodzaje zmiennych i stałych

Do tej pory używaliśmy tylko zmiennych, które były deklarowane za pomocą słowa let. Oprócz nich musisz również poznać stałe const oraz dowiedzieć się o zmiennych var. Zanim jednak do nich przejdziemy, wprowadzimy temat zakresy zmiennej.

Zakres zmiennej

Przy dużych projektach, oprócz konieczności deklaracji zmiennych, paląca staje się jeszcze inna kwestia – prawidłowe używanie zakresu zmiennych.

Czym jest zakres zmiennej? Najprościej rzecz ujmując, jest to "przestrzeń" w której istnieje ta zmienna. Innymi słowy, zakres decyduje o tym, gdzie możemy użyć jej użyć.

Przejdźmy od razu do konkretnego przykładu:

let count = 0;

if(count == 0){
  count = 1;
  let sum = 1;
}

console.log('count is', count); // count is 1
console.log('sum is', sum); // ERROR: sum is not defined

Dlaczego ostatnia linia z powyższego przykładu spowoduje błąd? Ponieważ zmienna sum została zdefiniowana wewnątrz bloku if, a zakresem zmiennej let jest blok kodu zamknięty w nawiasy klamrowe { }.

Zmienna count została zdefiniowana poza blokiem if, więc możemy wyświetlić jej wartość, ale zmienna sum "istnieje" tylko wewnątrz bloku if, i tylko w nim możemy się do niej odnosić.

W tym przykładzie zmienną count nazwalibyśmy zmienną globalną – jest zdefiniowana poza jakimkolwiek blokiem { }, więc będzie dostępna nie tylko w całej reszcie tego samego pliku JS, ale w całym kodzie JS (o ile jest wykonywany po wykonaniu tej deklaracji).

Możesz w pierwszej chwili pomyśleć sobie, że pilnowanie zakresu zmiennych będzie Ci tylko komplikować życie. Może lepiej, żeby wszystkie zmienne były globalne? Nic bardziej mylnego!

Gdyby wszystkie zmienne były globalne, tzn. miały zakres całego skryptu, musielibyśmy bardzo ostrożnie je nazywać. Przecież gdzieś, w jakimś miejscu w kodzie, mogliśmy już użyć zmiennej computerMove. W innym miejscu kodu musielibyśmy użyć innej nazwy. Łatwo sobie wyobrazić, że przy skrypcie liczącym tysiące linii kodu, to może (a wręcz – będzie) powodować sporo problemów i nieczytelne nazwy zmiennych.

Właśnie z tego względu będziemy starali się, aby nasze zmienne miały jak najmniejszy zakres. Będą istniały tylko tam, gdzie są nam potrzebne. Co więcej, będziemy unikać stosowania zmiennych globalnych i z tego względu całą zawartość pliku będziemy zamykać w nawiasach klamrowych.

Stała const

Oprócz zmiennych możemy też używać stałych – zamiast let napiszemy wtedy const. Różnią się one od zmiennych tym, że nie można później zmienić ich wartości. Dlaczego warto ich używać? Czasami możemy chcieć się upewnić, że przypadkiem nie zmienimy wartości zmiennej – wtedy warto użyć stałej. Dzięki temu nie musimy ani pamiętać, ani sprawdzać, czy jakaś nazwa zmiennej została już wykorzystana.

Dlatego możesz przyjąć, że zawsze używamy stałych, a tylko tam, gdzie jest taka potrzeba, będziemy używać zmiennych. Nie musisz się nad tym zastanawiać przy deklarowaniu zmiennej/stałej – możesz zawsze deklarować stałą, a dopiero w razie potrzeby zmienić const na let.

Pamiętaj, że przy deklaracji stałej musimy od razu nadać jej wartość. Inaczej jej istnienie nie miałoby sensu – skoro nie możemy później zmienić wartości, nie ma powodu deklarowania stałej bez nadawania jej wartości.

Zmierzch zmiennych var

Zarówno na samym początku nauki programowania, jak i posiadając lata doświadczenia, często będziesz szukać w internecie rozwiązań problemów, czy odpowiedzi na nurtujące Cię pytania. Dlatego warto poznać również zmienne var, mimo że dziś już raczej unika się ich używania.

JavaScript ewoluuje, jak każdy język programowania, ewoluuje. Do 2015 roku, kiedy został opublikowany standard ES6, w JS-ie nie istniały zmienne let ani stałe const – zamiast nich zawsze używało się właśnie zmiennych var.

Standardy ES6

W wielkim skrócie jest to "nowa" wersja JavaScriptu, której pełna nazwa to ECMAScript 2015. Wprowadziła ona wiele zmian do JS-a, w tym m.in. zmienne let i stałe const.

Uczymy Cię od razu standardów ES6, mimo że nie są one wspierane przez niektóre – niestety wciąż popularne – przeglądarki, takie jak Internet Explorer 11. Wiele software house'ów nadal wspiera IE11, ale nie przejmujemy się tym, ponieważ:

  • zanim ukończysz kurs i znajdziesz pracę, ta sytuacja może się zmienić,
  • za kilka tygodni poznasz narzędzie, które będzie na podstawie naszego kodu JS tworzyć wersję kompatybilną ze starszymi przeglądarkami.

Dlatego ważniejsze jest, aby poznawać od razu standardy, w których obecnie są tworzone projekty.

PS: Jeśli mamy być bardzo dokładni, to ES6 nie jest wersją JavaScriptu, tylko wersją ECMAScript – standardu tworzenia języków skryptowych, na którym opiera się JavaScript. W praktyce jednak ES6 traktuje się jako określenie zbioru nowych funkcjonalności JS-a.

Czym zmienne var różnią się od zmiennych let? W praktyce główną różnicą jest zakres zmiennej. Podczas gdy zakresem zmiennej let jest blok { }, to w przypadku zmiennej var jest to funkcja.

Spójrz na ten przykład:

function differenceBetweenNumbers(argNumA, argNumB){
  if(argNumA > argNumB){
    var numAIsBigger = true;
  }

  if(numAIsBigger){
    return argNumA - argNumB;
  } else {
    return argNumB - argNumA;
  }
}

Ten kod zadziała poprawnie, mimo że zmienna numAIsBigger została zadeklarowana wewnątrz bloku if. Jej zakresem jest cała funkcja, dlatego będzie dostępna również poza pierwszym blokiem if.

Gdybyśmy zamiast niej chcieli użyć zmiennej let, musielibyśmy zadeklarować zmienną na wyższym poziomie, czyli poza blokiem if:

function differenceBetweenNumbers(argNumA, argNumB){
  let numAIsBigger;

  if(argNumA > argNumB){
    numAIsBigger = true;
  }

  if(numAIsBigger){
    return argNumA - argNumB;
  } else {
    return argNumB - argNumA;
  }
}

Różnica zakresu jest największą zmianą, ale nie jedyną – np. zmienną var można też wielokrotnie deklarować, co zawsze było złą praktyką, ale możesz się z tym spotkać w przykładach kodu znalezionych w internecie. Kolejne deklaracje zmiennej var są traktowane po prostu jako przypisanie wartości (lub ignorowane, jeśli nie ma w nich przypisania wartości).

Deklarowanie funkcji nazwanych

Kolejną istotną kwestią, która łączy się z tematem zmiennych, jest zakres funkcji nazwanej – tzn. takiej, która nie jest anonimowa, czyli po function ma podaną nazwę. Funkcje nazwane są traktowane przez JS inaczej niż reszta kodu – są one parsowane (analizowane przez silnik JS-a) przed wykonaniem kodu z danego pliku. Spójrz na poniższy przykład:

saySomething();

function saySomething(){
  console.log('Hi!');
}

saySomething();

function saySomething(){
  console.log('Bye!');
}

Czytając ten kod, mogłoby Ci się wydawać, że pierwsze wywołanie saySomething() powinno powodować błąd. Gdyby go nie było, drugie wywołanie powinno wyświetlić "Hi!". Prawda?

Funkcje nazwane są jednak parsowane przed resztą kodu, w związku z czym najpierw zostanie stworzona funkcja saySomething wyświetlająca "Hi!", potem zostanie ona nadpisana nową funkcją o tej samej nazwie, która wyświetli "Bye!", a dopiero potem zostaną wykonane oba wywołania tej funkcji – i oba wyświetlą "Bye!"...

To może powodować, że ciężej będzie Ci zrozumieć kod aplikacji i odnaleźć potencjalne błędy. Dlatego zachęcamy Cię do używania funkcji anonimowych i zapisywania ich w stałych (lub ew. zmiennych).

const saySomething = function(){
  console.log('Hi!');
};

Dzięki temu nie musisz pamiętać jakimi prawami rządzą się funkcje nazwane, ponieważ w tym przypadku zastosowanie będą miały zasady dotyczące zakresu zmiennych i stałych.

Deklarowanie wielu zmiennych

Jeżeli chcemy zadeklarować wiele zmiennych lub stałych, nie musimy za każdym razem powtarzać słów let lub const. Wystarczy użyć ich raz, a potem po przecinku wymienić zmienne lub stałe, które chcemy zadeklarować:

let button,
  activeClass = 'active',
  description;

Używanie tej pisowni zależy od osobistych (lub zespołowych) preferencji.

Deklarowanie argumentów funkcji

Argumenty funkcji są definiowane poprzez wymienienie ich w nawiasach okrągłych ( ) przy deklaracji funkcji. Nie ma potrzeby dodatkowego używania słów let czy const:

const quoteText = function (quoted){
  return '<quote>' + quoted + '</quote>';
}

Podsumowanie

W tym submodule pojawiło się sporo nowych informacji. Teraz kiedy już rozumiesz te zagadnienia, możemy je bardzo krótko podsumować. Dzięki temu lepiej zapamiętasz wszystko, co potrzebujesz już teraz wiedzieć na temat zmiennych i stałych.

Deklarowanie zmiennych i stałych

Poprawne sposoby deklaracji zmiennych, stałych i argumentów to:

// Zdefiniowanie zmiennej bez przypisanej wartości

let players;

// Zdefiniowanie zmiennej z przypisaną wartości

let amount = 20.5;

// Zdefiniowanie stałej z przypisaną wartości

const homeUrl = 'https://google.com';

// Definiowanie wielu stałych

const activeClass = 'active',
  highlightClass = 'highlight';

// Definiowanie wielu zmiennych

let button,
  pointsSum = 0;

Pamiętaj, że domyślnie używamy stałych, a tylko tam, gdzie jest taka potrzeba – zmiennych.

Argumenty funkcji

Definiowanie argumentów funkcji nie wymaga użycia słów let ani const.

// Deklaracja funkcji z użyciem argumentu

const quoteText = function (quoted){
  return '<quote>' + quoted + '</quote>';
}

Zakres zmiennych i stałych

Zmienna istnieje tylko w swoim zakresie i chcemy, żeby zakres zmiennej był najmniejszy jak to możliwe. Innymi słowy, stosujemy większy zakres zmiennej, tylko jeśli w danym przypadku jest to nam potrzebne.

Zmienne let i stałe const mają zakres najbliższego bloku, zamkniętego w nawiasy klamrowe { }. Jeśli potrzebujemy, aby zakres zmiennej lub stałej był większy, deklarujemy ją wcześniej. Staramy się jednak unikać deklarowania zmiennych globalnych, czyli deklarowania poza jakąkolwiek funkcją czy blokiem, np. na samym początku pliku.

Zadanie: Poprawa kodu projektu

Teraz czas na zastosowanie wiedzy o zmiennych w praktyce! Otwórz swój projekt z poprzedniego modułu i znajdź w nim plik js/script.js. Twoim zadaniem jest poprawa deklaracji zmiennych zgodnie z informacjami z tego submodułu, czyli:

  • cały kod znajdujący się w tym pliku zamknij w bloku za pomocą nawiasów klamrowych { }, aby unikać używania zmiennych/stałych globalnych,
  • jeśli to możliwe, używaj stałych,
  • deklaracje stałych/zmiennych powinny być – w miarę możliwości – zagnieżdżone tak samo, jak miejsce ich wykorzystania, czyli staramy się używać możliwie małych zakresów,
  • unikaj sytuacji, w których tuż pod deklaracją zmiennej nadajesz jej wartość – lepiej zrobić to od razu w deklaracji,
  • zamień funkcje nazwane na funkcje anonimowe zapisane w stałych,

Testuj krok po kroku!

To bardzo ważne – nie zmieniaj wszystkiego naraz! Zmień jedną zmienną na stałą i wtedy sprawdź, czy skrypt działa, a w konsoli nie ma błędów. Po kilku zmianach zapisz commit i wtedy pracuj dalej.

To może wydawać się oczywiste, ale łatwo się zapomnieć i zrobić wiele zmian w kodzie. Potem testujemy i zaczynamy przeprawę przez morze błędów, wątpiąc, co zrobiliśmy dobrze, a co źle. Pamiętaj też o commitowaniu po każdej przetestowanej zmianie – dzięki temu zawsze możesz wrócić do ostatniej działającej wersji.

Po zakończeniu zadania upewnij się, że wszystkie zmiany są zapisane w commitach. Wtedy wyślij je na zdalne repozytorium, a link do najnowszego commita wyślij do sprawdzenia.

6.2. Tworzymy bloga!

Wyposażeni w powyższą wiedzę, możemy przystąpić do działania!

Jak pamiętasz, nad projektem będziemy pracować przez dwa moduły. Naszym zadaniem będzie stworzenie prostej aplikacji bloga. Jak zwykle, zaczniemy od podstawowej wersji (stworzymy ją w ramach bieżącego modułu), a skończymy na stronie z działającym skryptem (w kolejnym tygodniu).

Dzięki temu projektowi poznasz wiele nowych zagadnień – będą to przede wszystkim: tablice, obiekty i pętle. Zacznijmy jednak od sprecyzowania, co chcemy zrobić.

Specyfikacja projektu

Naszym celem będzie stworzenie strony, która składa się z:

  • nagłówka,
  • lewej kolumny, w której znajdą się tytuły artykułów,
  • środkowej kolumny, która wyświetla jeden artykuł,
  • prawej kolumny, która zawiera chmurę tagów oraz listę autorów.

Czym jest chmura tagów?

Nie wszyscy spotkali się z tym określeniem, więc wyjaśniamy – tagi to słowa kluczowe związane np. z artykułem. Każdy post na naszym blogu może mieć wiele tagów.

image

Chmura tagów to lista, w której wielkość czcionki zależy od tego, jak często występuje dane słowo kluczowe. Np. tag występujący w dziesięciu artykułach będzie większą czcionką niż ten występujący tylko w trzech.

Działanie projektu:

  • w kodzie HTML będą znajdować się wyłącznie artykuły,
  • po otwarciu strony, za pomocą JS zostanie wygenerowana lista tytułów, chmura tagów, oraz lista autorów,
  • po kliknięciu któregoś z tytułów w lewej kolumnie, odpowiedni artykuł wyświetli się w środkowej kolumnie,
  • po kliknięciu któregoś tagu lub autora, lista tytułów w lewej kolumnie zostanie przefiltrowana.

Możliwe zastosowania projektu

Będziemy nazywać ten projekt blogiem, ale nic nie stoi na przeszkodzie, aby w Twojej implementacji stał się czymś innym. Najważniejsze, aby każdy artykuł posiadał jedno pole, które może zawierać wiele wartości (post na blogu może mieć wiele tagów), oraz drugie – z tylko jedną wartością (post na blogu ma jednego autora).

Kilka propozycji zastosowania:

  • portfolio programisty
    • tagi – kluczowe cechy projektu i technologie w nim wykorzystane, np. slideshow
    • zamiast autorów – przeznaczenie projektu, np. portfolio fotografa
  • książka kucharska
    • tagi – nazwy składników,
    • zamiast autorów – rodzaje dań, np. przystawka,
  • katalog ulubionych filmów
    • tagi – gatunki, aktorzy,
    • zamiast autorów – reżyser filmu,
  • notatki z nauki (web developmentu, obcego języka, etc.)
    • tagi – słowa kluczowe notatki, np. funkcje,
    • zamiast autorów – główna kategoria notatki, np. JavaScript,

To tylko parę pomysłów – z pewnością przyjdą Ci do głowy również inne, które możesz wykorzystać w tym projekcie. Dzięki temu Twój projekt będzie unikatowy, a praca nad nim będzie przyjemniejsza – co z kolei wpłynie na większą wydajność nauki!

Od czego zacząć?

Przygotowaliśmy dla Ciebie prosty szablon naszej strony, żebyśmy mogli od razu przejść do pisania kodu JS. Postaraj się na razie nie poświęcać zbyt wiele czasu na zmianę wyglądu strony – przyjdzie na to czas pod koniec tego modułu. Pod podglądem znajdziesz instrukcje konfiguracji środowiska pracy.


Przygotowanie środowiska pracy

Podobnie jak w poprzednim module, przygotuj środowisko nowego projektu o nazwie javascript-blog, korzystając z poniższych kroków:

  1. Stwórz na GitHubie nowe repozytorium pod nazwą javascript-blog.
  2. Sklonuj stworzone repozytorium na swój komputer.
  3. Skopiuj plik package.json stworzony w poprzednim module do katalogu projektu.
  4. Zainicjuj projekt za pomocą komendy npm run init-project uruchomionej w katalogu projektu.
  5. Zapisz commit i wyślij go (wykonaj push) do zdalnego repozytorium.
  6. Sprawdź w panelu GitHub, czy pliki są widoczne w repozytorium.
  7. W pliku index.html stwórz podstawową strukturę strony (doctype, html, head, meta, title, body).
  8. Podlinkuj plik css/style.css za pomocą tagu link.
  9. Skopiuj kod HTML z szablonu załączonego powyżej i wstaw go wewnątrz tagu body.
  10. Przed zamknięciem tagu body (czyli przed tagiem </body>) załącz plik script.js w następujący sposób: <script src="js/script.js"></script>
  11. Skopiuj kod SCSS z powyższego szablonu i wklej go do swojego pliku style.scss.

Modyfikacja task runnera

Począwszy od tego modułu, będziemy pracować również na plikach .js, więc warto zmodyfikować nasz task runner, aby reagował na zmiany w tych plikach i automatycznie odświeżał podgląd – do tej pory ta funkcjonalność działała tylko dla plików .html i .css. W związku z tym zmień poniższą linijkę w obecnym task runnerze:

"watch:browsersync": "browser-sync start --server --files \"css/*.css\" \"*.html\"",

na następującą:

"watch:browsersync": "browser-sync start --server --files \"css/*.css\" \"*.html\" \"js/*.js\"",

Gotowe! Upewnij się, że zmiany we wszystkich powyższych plikach zostały zachowane i zapisz nowy commit.

Z tak przygotowanym środowiskiem jesteśmy gotowi do rozpoczęcia pisania naszego skryptu!

Dobre praktyki w JS

Zanim rozpoczniemy pracę, krótko wspomnimy o dobrych praktykach, które będą dla Ciebie istotne w trakcie dalszej nauki JS-a.

Średniki

W poprzednim module wspomnieliśmy, że na końcu każdej linii znajduje się średnik, i że później wspomnimy o wyjątkach. Teraz jest to "później". :)

Średników nie stawiamy po:

  • deklaracjach funkcji nazwanych, czyli np. function myFunc(){},
  • blokach if, else if i else,
  • pętlach, z których pierwszą poznasz w tym module.

A co się stanie, kiedy zapomnę postawić średnik? Odpowiedź na to pytanie może Cię zdziwić – nic się nie stanie. Wszystko zadziała tak samo. Używanie średników jest konwencją, którą stosuje większość programistów JS, i głównie dlatego uczymy Cię tego podejścia. Dzięki temu będą większe szanse, że Twoje przyzwyczajenia będą zgodne z przyzwyczajeniami innych członków Twojego zespołu, kiedy rozpoczniesz pracę jako Junior Web Developer.

Wcięcia

Jak już pewnie udało Ci się zauważyć, kod JS ma wcięcia. Podobnie jak w CSS oraz SCSS, wcięcia oznaczają przejście na kolejny poziom. Dzięki temu dużo łatwiej jest znaleźć koniec deklaracji funkcji lub bloku if/else.

Dbaj o porządek we wcięciach kodu! To znacznie ułatwi Ci rozumienie JS-a!

Strict mode

Na początku każdego pliku, zawierającego kod JS, będziemy umieszczać następującą linię:

'use strict';

Dzięki niej nasz kod będzie uruchamiany w trybie ścisłym. Pomyłki, które normalnie nie wywołałyby błędu, teraz będą traktowane jak błąd i wyświetlane na czerwono w konsoli, znajdującej się w narzędziach developerskich przeglądarki.

Możesz pomyśleć: "nie chcę mieć więcej błędów!", ale możesz nam wierzyć, że chcesz! Spójrz na poniższy przykład, w którym zrobiliśmy literówkę.

let counter = 0;

caunter = counter + 1;

console.log(counter); // 0

Wyobraź sobie, że popełniasz taką pomyłkę, ukrytą gdzieś pośród setek linii kodu. Twój kod nie działa poprawnie, ale konsola nie wyświetla żadnych przydatnych informacji. Rwiesz włosy z głowy jak tysiące programistów przed Tobą...

Właśnie dlatego chcemy używać 'use strict' – wtedy ten błąd byłby od razu wychwycony i wyświetlony w konsoli!

6.3. Wyświetlanie artykułu po kliknięciu

Podstawową funkcjonalnością naszego bloga ma być wyświetlanie artykułu po kliknięciu w jego tytuł w lewej kolumnie. Skorzystamy tutaj z rozwiązań, które poznaliśmy już w poprzednim module. Zacznijmy jednak od doprecyzowania działania tej funkcjonalności.

Na razie skupimy się tylko na kliknięciu w link, który został dodany w pliku index.html – dopiero później zastanowimy się, w jaki sposób generować listę linków. Przejdźmy więc do algorytmu naszej pierwszej funkcjonalności!

Algorytm działania skryptu

Wiemy już, że możemy powiązać kliknięcie w guzik (lub – w tym wypadku – link) z wykonaniem funkcji. Skąd jednak mamy wiedzieć, że dany link ma wyświetlić ten konkretny artykuł? Jak pamiętasz z modułu o Bootstrapie, skrypt musi "wiedzieć", że jeden element ma wpływać na drugi. Musimy je jakoś ze sobą powiązać!

Spójrz na kod HTML naszego bloga. Lista tytułów składa się z linków, których atrybuty href zostały wypełnione. Bliższa analiza kodu pokazuje, że wartości tych atrybutów to symbol #, po którym podane jest id powiązanego artykułu. Za chwilę wykorzystamy tę zbieżność w naszym kodzie!

image

Skoro już wiemy, w jaki sposób link z listy tytułów jest powiązany z artykułem, to w jaki sposób będziemy wyświetlać i ukrywać artykuły? Jeżeli przyjrzysz się SCSS-owi z naszego szablonu, zauważysz, że domyślnie wszystkie artykuły na blogu (elementy o klasie .post) są ukryte za pomocą deklaracji display: none;. Wyświetlane są tylko artykuły, które posiadają dodatkowo klasę active, która nadaje elementowi właściwość display: block;. Możemy zatem za pomocą JS dynamicznie nadawać i usuwać klasę active, aby pokazywać lub ukrywać artykuł.

Zostaje jeszcze jedna ważna kwestia: chcielibyśmy, żeby kliknięty link zmieniał kolor. W ten sposób oznaczymy na liście tytuł aktualnie wyświetlanego artykułu. W tym przypadku również wykorzystamy klasę active.

W takim razie algorytm działania skryptu będzie taki:

  • po kliknięciu linka:
    • ustaw klasy linków:
      • usuń klasę active ze wszystkich linków na liście tytułów,
      • dodaj klasę active do klikniętego linka,
    • ukryj wszystkie artykuły:
      • usuń klasę active ze wszystkich artykułów,
    • znajdź artykuł do wyświetlenia:
      • z klikniętego linka weź zawartość atrybutu href, np. #article-2,
      • znajdź na stronie element pasujący do selektora takiego, jak wartość atrybutu href, np. #article-2 – czyli szukamy elementu o id="article-2",
    • wyświetl znaleziony artykuł:
      • dodaj klasę active do znalezionego artykułu.

Z tak rozpisanym algorytmem jesteśmy prawie gotowi do pisania skryptu! Prawie, bo jeszcze nie wszystko z tej listy umiemy zrobić – ale będziemy to tłumaczyć na bieżąco.

Zdarzenia, czyli eventy

Podobnie jak w poprzednim module, napiszemy funkcję, która będzie działać po kliknięciu elementu na stronie. Zanim do tego przejdziemy, warto wspomnieć, że kliknięcie jest zdarzeniem, czyli eventem.

W JavaScripcie spotkasz się z wieloma różnymi eventami. Możesz nawet tworzyć własne i wykorzystywać do budowania lepszej architektury skryptu. Zacznijmy jednak od krótkiego wyjaśnienia, czym są eventy.

Metafora — event

Event możesz wyobrazić sobie jako zdarzenie, które każdy może zaobserwować. Przykładem może być zapalenie się zielonego światła na skrzyżowaniu.

To samo zdarzenie będzie miało różne skutki dla różnych osób:

  • piesi w ogóle nie patrzą na sygnalizator dla samochodów, więc w żaden sposób nie zareagują na zapalenie się zielonego światła,
  • kierowcy jadący na wprost ruszą i przejadą przez skrzyżowanie,
  • kierowcy skręcający w prawo będą mogli skręcić, ale muszą najpierw udzielić pierwszeństwa pieszym przechodzącym przez jezdnię, w którą chcą skręcić,
  • kierowca karetki jadącej na sygnale nie stosuje się do sygnalizacji, ale zielone światło do jazdy na wprost mówi mu, że nie musi się obawiać samochodu wjeżdżającego na skrzyżowanie z innego kierunku.

Zwróć uwagę, że nie każdy na skrzyżowaniu patrzy na to światło, i nie każdy, kto je obserwuje, zareaguje w ten sam sposób.

W JavaScripcie, do każdego elementu na stronie, można dodać "nasłuchiwacz zdarzeń". Brzmi to dużo zgrabniej po angielsku – event listener. Będzie on czekać na konkretny event występujący na tym elemencie. Kiedy się doczeka, wykona wcześniej zdefiniowaną funkcję, której przekaże informacje o tym evencie.

Rozpoczynając pracę z eventami, warto wspomnieć o kluczowej zasadzie ich działania. Zastanów się nad taką sytuacją: mamy link, w którym umieściliśmy obrazek. Czy kliknięcie obrazka oznacza też kliknięcie linka? Musi tak być, bo inaczej musielibyśmy przypisać funkcję do kliknięcia każdego elementu w linku!

W JS zostało to rozwiązane za pomocą bąbelkowania, czy bardziej profesjonalnie – propagacji. W podanym przykładzie obrazek otrzyma event click. Następnie przekaże ten event swojemu rodzicowi – linkowi. Dzięki temu link też będzie mógł zareagować na kliknięcie. Link również przekaże ten event swojemu rodzicowi – i tak event będzie bąbelkować do samej góry, aż do body, document i wreszcie window.

Funkcja addEventListener

W poprzednim module mieliśmy już do czynienia z tą funkcją przy obsługiwaniu kliknięcia guzika:

document.getElementById('test-button').addEventListener('click', function(){
  console.log('Guzik został kliknięty');
});

Funkcja czy metoda?

W tym module używamy określenia "funkcja", aby nie komplikować sytuacji. W następnym module dowiesz się, że kiedy funkcję wykonujemy po kropce, to nazywa się ona metodą. Technicznie rzecz biorąc, każda metoda jest funkcją, tak jak każdy kwadrat jest jednocześnie prostokątem, jednak w praktyce używa się dokładniejszego określenia – metoda. Wspominamy o tym teraz, aby nie zdziwiło Cię, kiedy spotkasz się z tym określeniem.

Przeanalizujmy ostatnią linię powyższego kodu:

  • document.getElementById('test-button') znajduje na stronie element o id="test-button"),
  • .addEventListener jest wybraniem funkcji addEventListener przypisanej do tego guzika – czyli chcemy powiązać jakąś funkcję z jakimś eventem występującym na tym elemencie,
  • ( ) – te nawiasy, umieszczone bezpośrednio po nazwie funkcji, mówią nam, że wykonujemy tę funkcję,
    • 'click' to rodzaj eventu, którego będziemy nasłuchiwać,
    • function rozpoczyna anonimową funkcję, która ma zostać wykonana po wystąpieniu tego eventu na tym elemencie.

Przy okazji warto wspomnieć, że funkcja wykonywana w reakcji na event jest często nazywana handlerem. Innymi słowy, handler eventu zostanie wykonany przez listener w momencie wykrycia eventu danego typu.

Jak znaleźć wszystkie linki?

Wiemy już sporo o eventach i ich listenerach, więc czas zastosować tę wiedzę do linków na liście tytułów. Moglibyśmy każdemu z nich nadać id i wykorzystać powyższy fragment kodu, ale musielibyśmy to zrobić wiele razy – i dodawać kolejne fragmenty kodu JS przy każdym dodaniu nowego artykułu.

Jak pewnie się domyślasz, jest na to lepszy sposób. Wykorzystamy jedną z dwóch funkcji, która pozwala na znalezienie elementu za pomocą selektora CSS! To się świetnie składa, bo znasz już działanie selektorów!

Mamy do dyspozycji dwie funkcje:

  • document.querySelector(selector), która wyszuka pierwszy pasujący element,
  • document.querySelectorAll(selector), która wyszuka wszystkie elementy, pasujące do selektora.

Będzie nas interesować druga metoda, więc to właśnie ją zastosujemy w docelowym skrypcie. Przećwiczmy ją teraz i wykorzystajmy wcześniejszy kod do wyświetlenia w konsoli listy linków po kliknięciu w guzik:

  1. W swoim pliku index.html, w dowolnym miejscu (np. w lewym sidebarze nad listą linków) dodaj element button z atrybutem id="test-button".
  2. W pliku js/script.js wklej poniższy kod:
document.getElementById('test-button').addEventListener('click', function(){
  const links = document.querySelectorAll('.titles a');
  console.log('links:', links);
});
  1. Otwórz konsolę w narzędziach developerskich i sprawdź, co się w niej pojawi po kliknięciu guzika, który przed chwilą dodaliśmy do strony.

Jak widzisz w konsoli, po kliknięciu guzika funkcja querySelectorAll znalazła wszystkie elementy pasujące do selektora .titles a. Koniecznie w konsoli kliknij trójkąt, który pojawił się przy wyświetlonym komunikacie. W ten sposób zobaczysz pełną zawartość stałej links!

image

Jeżeli udało Ci się uzyskać taki sam efekt – znakomicie! Twój kod działa, jak należy. Możesz teraz skasować button z pliku index.html oraz zakomentuj kod, który wstawiliśmy do pliku js/script.js.

Wiemy już, jak wyszukać wszystkie linki. Czy zatem teraz wystarczy napisać tylko links.addEventListener(...)? Niestety, nie będzie tak łatwo. Funkcji addEventListener możemy użyć tylko na pojedynczym elemencie. W takim razie musimy napisać pętlę...

Pętla for-of

Jeśli pamiętasz jeszcze rozdział o algorytmach z poprzedniego modułu, pokazaliśmy w nim zagadnienie pętli. Teraz poznamy pętle w JS.

Pętla to nic innego jak wykonywanie tych samych czynności w kółko. Każde wykonanie tych operacji, czyli każdy "obrót" pętli, nazywamy iteracją.

Metafora — pętla

Załóżmy, że Twój kot często chowa się w pudełku, kiedy chcesz go zabrać do weterynarza. Potrzebujesz w takim razie znaleźć kota, a masz przed sobą wiele pudełek.

Dla każdego pudełka musisz wykonać następujący algorytm:

  • jeśli pudełko jest za małe dla kota, przejść do kolejnego pudełka,
  • jeśli pudełko jest oklejone taśmą klejącą i kot nie mógł do niego wejść, przejść do kolejnego pudełka,
  • otwórz pudełko, i jeśli nie ma w nim kota, przejść do kolejnego pudełka,
  • jeśli jest w nim kot, wyjmij kota i nie sprawdzać kolejnych pudełek.

W przypadku naszego skryptu, mamy stałą links, która zawiera jakąś liczbę linków. Pisząc nasz skrypt, nie wiemy ile linków będzie – i nie potrzebujemy tego wiedzieć. Pętla ma coś zrobić dla każdego z nich. Składnia pętli w naszym skrypcie będzie wyglądała następująco:

const links = document.querySelectorAll('.titles a');

for(let link of links){
  console.log(link);
}

Powyższy przykład po prostu wyświetli w konsoli każdy element, zawarty w links.

Przeanalizujmy składnię tej pętli:

  • for – to będzie pętla,
  • ( ) – w tych nawiasach zawieramy definicję działania pętli,
    • let link – deklarujemy zmienną, w której znajdzie się pojedynczy element z links,
    • of – łącznik, który wyjaśnimy w następnym module,
    • links – kolekcja, z której mają być brane elementy, po których iterujemy pętlę,
  • { } – w tych nawiasach wpisujemy operacje, które mają być wykonane dla każdego iterowanego elementu.

Zobaczmy więc, w jaki sposób możemy wykorzystać pętlę do przypisania event listenerów do każdego linka. Wstaw w swoim pliku js/script.js poniższy kod, i obserwuj w konsoli developerskiej, co się dzieje, gdy klikasz linki na liście tytułów:

const titleClickHandler = function(){
  console.log('Link was clicked!');
}

const links = document.querySelectorAll('.titles a');

for(let link of links){
  link.addEventListener('click', titleClickHandler);
}

Zwróć uwagę, że tym razem zapisaliśmy handler eventu w zmiennej titleClickHandler – za chwilę zacznie się on rozrastać, więc taki zapis będzie dla nas wygodniejszy, niż zastosowanie anonimowej funkcji bezpośrednio jako drugi argument funkcji addEventListener (wewnątrz pętli).

No dobrze, zaglądamy do konsoli i coś się tam wyświetla, ale... co właściwie chcieliśmy w ten sposób osiągnąć?

W trakcie pracy nad skryptem łatwo jest zapomnieć, jaki był algorytm. Dlatego warto przenieść go sobie bezpośrednio do kodu, który piszemy! Wykorzystamy do tego komentarze – oczywiście, zgodnie z dobrymi praktykami, napiszemy je po angielsku.

Doprowadź swój plik js/script.js do stanu pokazanego poniżej:

const titleClickHandler = function(event){
  console.log('Link was clicked!');

  /* remove class 'active' from all article links  */

  /* add class 'active' to the clicked link */

  /* remove class 'active' from all articles */

  /* get 'href' attribute from the clicked link */

  /* find the correct article using the selector (value of 'href' attribute) */

  /* add class 'active' to the correct article */
}

const links = document.querySelectorAll('.titles a');

for(let link of links){
  link.addEventListener('click', titleClickHandler);
}

Tym razem wykorzystaliśmy inny rodzaj komentarza – komentarz blokowy. W przeciwieństwie do komentarza rozpoczynającego się od //, ten rodzaj komentarza może mieć wiele linii. Tym razem jednak użyliśmy go tylko po to, aby wyglądał inaczej niż komentarze //, których będziemy używać do zamieszczania innych uwag.

To świetna metoda na ułatwienie sobie pracy. Od teraz będziemy wypełniać kod pod każdym komentarzem. Przy okazji od razu zrobimy sobie dokumentację naszego skryptu.

Ćwiczenie

Każdy handler eventu będzie otrzymywał argument, zawierający informacje na temat wychwyconego zdarzenia. Standardowo temu argumentowi nadaje się nazwę event, co też zrobiliśmy w powyższym przykładzie.

Do funkcji titleClickHandler dodaj console.log, który wyświetli zawartość argumentu event. Obejrzyj w konsoli jego zawartość.

image

Jak widzisz, jest w nim mnóstwo informacji i funkcji. Jedną z ciekawszych jest target, które pokazuje element, który został kliknięty. W naszym przykładzie, w każdym linku znajduje się <span>, a dopiero w nim tekst. Dlatego w argumencie event jako target podany jest span.

image

Dodaj kod z powyższego przykładu (razem z console.log dodanym w tym ćwiczeniu) do swojego pliku js/script.js. Zapisz commit i wyślij na zdalne repozytorium.

Zmiana klas elementu na stronie

Pierwszy fragment kodu do napisania w funkcji titleClickHandler to usunięcie klasy active ze wszystkich linków. Musimy w takim razie:

  • znaleźć wszystkie linki z klasą active,
  • zastosować pętlę, aby dla każdego z nich:
    • usunąć klasę active.

Umiesz już znaleźć wszystkie elementy pasujące do selektora oraz napisać pętlę, więc bez trudu zrozumiesz je w poniższym kodzie. Nowością będzie tylko sposób na usunięcie klasy z elementu. W swoim pliku js/script.js, pod linią komentarza /* remove class 'active' from all article links */, dodaj następujący kod:

const activeLinks = document.querySelectorAll('.titles a.active');

for(let activeLink of activeLinks){
  activeLink.classList.remove('active');
}

Skupmy się na chwilę na tej linii kodu:

activeLink.classList.remove('active');
  • activeLink to pojedynczy link, spośród wyszukanych linków z klasą active,
  • .classList to "biblioteka", zawierająca informacje i funkcje dotyczące klas tego elementu,
  • .remove to jedna z tych funkcji, służąca do usunięcia klasy,
  • ( ) – te nawiasy, umieszczone bezpośrednio po nazwie funkcji, mówią nam, że wykonujemy funkcję,
  • 'active' to klasa, którą chcemy usunąć.

Inne przydatne funkcje w classList, których możemy użyć analogicznie do remove, to:

  • add – dodawanie klasy,
  • toggle – "przełączanie" klasy, czyli "jeśli element nie ma podanej klasy, to ją dodaj – w przeciwnym wypadku usuń tę klasę z tego elementu",
  • contains – sprawdzenie, czy element posiada daną klasę.

Świetnie – usuwanie aktywnej klasy już działa. Możesz to sprawdzić, dodając któremuś linkowi klasę active w kodzie HTML. Po kliknięciu któregokolwiek linka klasa zostanie usunięta.

Ćwiczenie

Mamy już gotowy fragment funkcji titleClickHandler, zaczynający się od komentarza:

/* remove class 'active' from all article links  */

Poniżej znajduje się bardzo podobny komentarz:

/* remove class 'active' from all articles */

Uzupełnij ten fragment funkcjonalności, wykorzystując bardzo podobny kod – musisz wprowadzić tylko dwie zmiany:

  • zmień nazwy stałych i zmiennych z activeLinks i activeLink na activeArticles i activeArticle,
  • zmień selektor .titles a.active na odpowiedni dla artykułów znajdujących się w naszym blogu, które posiadają klasę active.

W efekcie tych zmian, po kliknięciu któregokolwiek linka w lewej kolumnie, artykuł w środkowej kolumnie powinien zniknąć. Nie pojawi się na jego miejsce żaden inny, ponieważ jeszcze nie nadajemy klasy active na żaden artykuł.

Nie zapomnij zapisać commita i wysłać go na zdalne repozytorium.

"Magiczne" słowo this

Kolejnym krokiem jest nadanie klasy active dla klikniętego linka. Domyślasz się pewnie, że pomoże nam w tym owo "magiczne" słowo this. Zobaczmy, jak zmieni się nasz kod, a później wyjaśnimy, dlaczego w ten sposób użyliśmy w nim this.

W komentarzach, za pomocą [DONE] oznaczyliśmy fragmenty funkcji titleClickHandler, które są już gotowe, lub zostały wykonane przez Ciebie w ramach poprzedniego ćwiczenia. Fragment, nad którym pracujemy teraz, oznaczyliśmy jako [IN PROGRESS].

Zastosuj teraz taką samą konwencję w swoim pliku js/script.js.

const titleClickHandler = function(event){
  const clickedElement = this;
  console.log('Link was clicked!');

  /* [DONE] remove class 'active' from all article links  */

  const activeLinks = document.querySelectorAll('.titles a.active');

  for(let activeLink of activeLinks){
    activeLink.classList.remove('active');
  }

  /* [IN PROGRESS] add class 'active' to the clicked link */

  console.log('clickedElement:', clickedElement);

  /* [DONE] remove class 'active' from all articles */

  /* get 'href' attribute from the clicked link */

  /* find the correct article using the selector (value of 'href' attribute) */

  /* add class 'active' to the correct article */
}

const links = document.querySelectorAll('.titles a');

for(let link of links){
  link.addEventListener('click', titleClickHandler);
}

Wewnątrz funkcji titleClickHandler mamy do dyspozycji dwa źródła informacji o evencie, czyli w naszym przypadku – o kliknięciu:

  1. argument funkcji, który nazwaliśmy event,
  2. obiekt this.

Wcześniej już sprawdzaliśmy, jakie informacje znajdują się w argumencie event. Znaleźliśmy w nim m.in. informację target, która jednak w naszym przypadku zawierała odniesienie do spana, znajdującego się w linku. Moglibyśmy jednak skorzystać z currentTarget zamiast target – tam znaleźlibyśmy odniesienie do elementu, do którego dodaliśmy listener. Ta sama informacja znajdzie się jednak w obiekcie this, a jego znajomość jest bardzo ważna, więc to na nim się skupimy.

Słowo this określiliśmy jako "magiczne", ponieważ – szczególnie na początku nauki JS – wydaje się nieco tajemnicze. Wynika to z faktu, że zmienia ono znaczenie, w zależności od tego, gdzie zostało użyte. Dlatego przyjmiemy zasadę, że dla unikania błędów i zwiększenia czytelności kodu, będziemy go używać jak najrzadziej – zapiszemy jego wartość do stałej, która będzie miała bardziej przyjazną nazwę.

W przypadku handlera eventu click, pierwszą linijką funkcji niech zawsze będzie:

const clickedElement = this;

Dzięki temu w całej funkcji będziemy mogli używać clickedElement, bez zastanawiania się, co w tym miejscu oznacza this.

Przy okazji zauważ, że tym razem inaczej użyliśmy console.log. Zamiast łączyć teksty za pomocą znaku +, podaliśmy dwa argumenty, rozdzielając je przecinkiem. Dzięki temu zobaczymy znacznie więcej informacji o sprawdzanej zmiennej (lub stałej, argumencie czy obiekcie). Sprawdź jak zmieni się komunikat w konsoli, jeśli użyjesz np. console.log('clickedElement (with plus): ' + clickedElement);.

Ćwiczenie

Mamy już odniesienie do klikniętego linka, zapisane w stałej clickedElement. Twoim zadaniem jest dodanie mu klasy active.

Pamiętaj, że nie potrzebujesz pętli, ponieważ clickedElement jest odniesieniem do pojedynczego elementu, a nie grupy elementów. W takim razie wystarczy jedna linijka kodu, w której zamiast usuwania klasy (remove) będziemy dodawać klasę (add).

Już wcześniej po kliknięciu któregokolwiek linka w lewej kolumnie, aktywny link tracił klasę active. W efekcie tego ćwiczenia, kliknięty link powinien otrzymywać klasę active – dzięki temu ostatnio kliknięty link powinien być podświetlony!

Nie zapomnij zapisać commita i wysłać go na zdalne repozytorium.

Blokowanie domyślnej akcji

Jak dobrze wiesz, kliknięcie linka na stronie zmienia adres strony. Jeśli wartość atrybutu href będzie się zaczynać od #, to zmieni się tylko końcówka adresu. Ten fragment URL-a strony, od znaku # do końca, nazywa się określeniem hash.

Zmiana hasha adresu strony ma domyślne zastosowanie – przewija stronę tak, aby element o takim samym id znajdował się na samej górze okna. Jest to mechanizm pozwalający na proste przewijanie strony do odpowiedniej sekcji. Dla przykładu, spis treści artykułu na Wikipedii działa dokładnie w ten sposób – po kliknięciu tytułu rozdziału w spisie treści zostajemy natychmiast przeniesieni do odpowiedniego rozdziału.

Na naszym blogu nie potrzebujemy tego mechanizmu, a wręcz przeciwnie – byłby on irytujący dla użytkownika. Dlatego skorzystamy z możliwości wyłączenia domyślnego zachowania przeglądarki przy kliknięciu w linki, które obsługujemy za pomocą naszego skryptu JS.

Wystarczy, że w handlerze eventu (w naszym przypadku jest to funkcja titleClickHandler) dodamy linię:

event.preventDefault();

Jak widzisz, implementacja rozwiązania jest dużo prostsza, niż wytłumaczenie tej kwestii. Zwróć uwagę, że wykorzystujemy tutaj argument event, który jest przyjmowany przez handler.

Zwyczajem jest dodawanie tej linii na początku funkcji, więc tak też zrobimy – dodaj ją przed deklaracją stałej clickedElement. Adres strony nie powinien się już zmieniać przy klikaniu w linki w lewej kolumnie.

W ten sam sposób możemy blokować domyślne zachowanie dowolnego eventu – np. częstym zastosowaniem jest blokowanie domyślnego działania formularzy, aby umożliwić sprawdzenie poprawności wypełnienia poszczególnych pól (walidacja), oraz wysłać formularz za pomocą JS-a, czyli bez przeładowania strony. Jednak do tego tematu jeszcze wrócimy. ;)

Zadanie: Dokończenie handlera eventu

W tym submodule znalazło się kilka ćwiczeń – ich wykonanie jest elementem tego zadania. Pozostały nam też do napisania trzy fragmenty funkcji titleClickHandler – to będzie pozostała część tego zadania. Jeśli czujesz się na siłach, spróbuj wykonać je bez opisu, załączonego poniżej.

Ważne: do wykonania zadania z pewnością przyda Ci się funkcja getAttribute.

Zajmijmy się po kolei fragmentami, które pozostały nam do uzupełnienia.

Pobranie atrybutu klikniętego linka

/* get 'href' attribute from the clicked link */

Umiemy już znaleźć kliknięty link – zapisaliśmy go w stałej clickedElement. Znajdziemy teraz jego atrybut href.

Zadeklarujmy nową stałą o nazwie articleSelector. Od razu możemy ustawić jej wartość, czyli wartość atrybutu href pobraną z klikniętego linka.

Spójrz na przykład znajdujący się w dokumentacji funkcji getAttribute – powinien podpowiedzieć Ci, w jaki sposób użyć tej funkcji. Pamiętaj, że musimy ją wykonać na elemencie clickedElement, a w nawiasach podać nazwę atrybutu w cudzysłowach, czyli href.

Użyj console.log dla stałej articleSelector, aby sprawdzić, czy udało się pobrać wartość tego atrybutu. Powinna być inna dla każdego linka, i wyświetlać się w konsoli po kliknięciu w link.

image

Wyszukanie właściwego artykułu

/* find the correct article using the selector (value of 'href' attribute) */

W stałej articleSelector mamy już zapisaną wartość atrybutu href klikniętego linka. Jak pamiętasz, href każdego linka był identyczny, jak id odpowiadającego mu artykułu. Czas to wykorzystać!

Zadeklaruj nową stałą o nazwie targetArticle. Jako jej wartość ustawimy odwołanie do artykułu. Do tej pory używaliśmy do tego querySelectorAll, ale skoro teraz szukamy tylko jednego elementu, możemy skorzystać z funkcji querySelector. Działa dokładnie tak samo, ale zwróci pojedynczy element, a nie kolekcję elementów (która działa inaczej, nawet jeśli zawiera w sobie tylko jeden element).

Innymi słowy: jako wartość stałej targetArticle ustaw querySelector, wyszukujący artykuł o danym atrybucie href. Może się to wydawać zagmatwane, ale w rzeczywistości jest bardzo proste – znaleźliśmy ten artykuł w poprzednim punkcie i zapisaliśmy w stałej!

Ponownie użyj console.log, aby sprawdzić, czy udało się znaleźć właściwy artykuł. Po kliknięciu któregoś linka w lewej kolumnie, w konsoli powinien zostać wyświetlony odpowiadający mu artykuł.

image

Dodanie klasy active na znalezionym artykule

/* add class 'active' to the correct article */

Ostatni krok zadania to dodanie klasy active artykułowi, który zapisaliśmy w stałej targetArticle. Wystarczy, że skopiujesz kod, za pomocą którego ustawiliśmy klasę active dla klikniętego linka, i zmienisz w niej element, któremu dodajesz tę klasę.

Po wykonaniu tego kroku zadanie powinno być skończone i działać tak, jak opisaliśmy poniżej.


Oczekiwany efekt zadania

Efektem tego zadania powinna być strona z blogiem, na której kliknięcie w którykolwiek link w lewej kolumnie powoduje:

  • usunięcie podświetlenia linka, który do tej pory był aktywny,
  • dodanie podświetlenia klikniętego linka,
  • ukrycie artykułu, który do tej pory był widoczny w środkowej kolumnie,
  • wyświetlenie w środkowej kolumnie artykułu powiązanego z klikniętym linkiem.
image

Wskazówka

Możesz łatwo sprawdzić w Inspektorze, czy nadawanie i usuwanie klasy przy kliknięciu działa jak należy. Kiedy napiszesz skrypt, zbadaj Inspektorem link na liście postów. Klikaj link na stronie, a w Inspektorze obserwuj, czy zmienia się jego klasa:

image

Po skończonej pracy

Przeczytaj jeszcze raz cały kod swojego skryptu i zapisz sobie wszystko, czego nie rozumiesz. Będzie to świetna lista pytań na spotkanie z Mentorem!

Nie zapomnij o zapisaniu commita, wypchnięciu go na zdalne repozytorium, oraz wysłaniu linka do najnowszego commita do sprawdzenia!

6.4. Generowanie listy tytułów

Poprzedni submoduł był spory, ale też dużo się w nim nauczyliśmy. Teraz będziemy mogli wykorzystać tę wiedzę do wprowadzania kolejnych funkcjonalności!

Na razie wszystko, co widzisz na stronie bloga, znajduje się w kodzie HTML. Następnym krokiem będzie wygenerowanie listy tytułów wszystkich artykułów i wstawienie jej do lewej kolumny.

Algorytm działania skryptu

Zaczynamy, jak zwykle, od zapisania algorytmu skryptu, który chcemy napisać. Tym razem chcemy dla każdego artykułu odnaleźć jego id oraz tytuł i wykorzystać je do stworzenia linka w lewej kolumnie.

Wiemy, że docelowo będziemy chcieli generować nową listę linków po każdym kliknięciu tagu lub autora. Dlatego w naszym skrypcie od razu uwzględnimy wyczyszczenie listy z dotychczasowej zawartości.

  • usuń zawartość listy linków w lewej kolumnie,
  • następnie dla każdego artykułu:
    • odczytaj jego id i zapisz je do stałej,
    • znajdź element z tytułem i zapisz jego zawartość do stałej,
    • na podstawie tych informacji stwórz kod HTML linka i zapisz go do stałej,
    • wstaw stworzony kod HTML do listy linków w lewej kolumnie.

Zastanówmy się, czy znamy już sposoby na wykonanie wszystkich kroków algorytmu:

  • zapisywanie wartości do stałej nie powinno już być problemem – znamy już zmienne let i stałe const,
  • umiemy znaleźć pojedynczy element za pomocą selektora CSS – wykorzystujemy funkcję querySelector,
  • w poprzednim module, w pliku functions.js poznaliśmy też sposób na odczytanie lub zmianę (w tym wyczyszczenie) zawartości elementu – za pomocą jego właściwości innerHTML,
  • niedawno nauczyliśmy się odczytywać atrybuty elementu, więc bez problemu odczytamy id elementu – wykorzystamy funkcję getAttribute,
  • stworzenie kodu HTML będzie po prostu połączeniem tekstu (kod HTML jest tekstem) ze stałymi przechowującymi id i tytuł artykułu,
  • dodawanie linka do listy można też wykonać za pomocą innerHTML.

Spróbuj samodzielnie!

Z powyższej listy wynika, że umiesz już wszystko, co potrzebne, aby poradzić sobie z tym zadaniem! Jeśli czujesz się na siłach, spróbuj samodzielnie stworzyć funkcję generateTitleLinks, która spełni powyższy algorytm.

Pamiętaj, żeby zacząć od zadeklarowania tej funkcji, a następnie umieść w niej console.log informujący o tym, że funkcja została wykonana. Warto dodać w niej również komentarze z algorytmem, który zapisaliśmy powyżej.

Nie zapomnij, że deklaracja funkcji sama się nie wykona — pod deklaracją dodaj jej wywołanie. Ta funkcja ma uruchamiać się od razu po odświeżeniu strony, więc nie musisz umieszczać wywołania w żadnej dodatkowej funkcji, ani tworzyć listenerów eventów.

Do samodzielnego wykonania tego zadania przyda Ci się informacja, że querySelector do tej pory wykonywaliśmy zawsze na elemencie document, ale równie dobrze możemy wykonać tę funkcję na dowolnym innym elemencie. Dzięki temu będziemy mogli wyszukać tytuł w konkretnym artykule!

Koniecznie przeczytaj też treść zadania na końcu tego submodułu! Powodzenia!

Dodawanie kodu w pliku script.js

Pamiętaj, żeby nie usuwać kodu, który masz już w pliku js/script.js. Kolejne skrypty – tak jak ten, który napiszemy w tym submodule – dodawaj pod obecnym kodem, ale wewnątrz bloku { } okalającego całą zawartość pliku.

Zacznijmy od przygotowania kodu, który opisaliśmy w poprzednim rozdziale. Tym razem zastosujemy dobrą praktykę, którą jest zapisanie "ustawień" skryptu w stałych. Dzięki temu będzie można łatwo przystosować kod np. do zmiany nazewnictwa klas w kodzie HTML. Aby odróżnić te stałe, ich nazwy zaczniemy od prefiksu opt – skrót od options.

const optArticleSelector = '.post',
  optTitleSelector = '.post-title',
  optTitleListSelector = '.titles';

function generateTitleLinks(){

  /* remove contents of titleList */

  /* for each article */

    /* get the article id */

    /* find the title element */

    /* get the title from the title element */

    /* create HTML of the link */

    /* insert link into titleList */

}

generateTitleLinks();

Mamy już gotowy szablon – teraz czas na rozpoczęcie pisania skryptu!

Usunięcie zawartości listy linków

Aby wyczyścić listę linków, musimy ją znaleźć, a następnie nadać jej pustą zawartość.

Wiemy, że szukamy jednego elementu, więc wykorzystamy querySelector. Tym razem jednak argumentem nie będzie tekst zamknięty w cudzysłowy, ale stała optTitleListSelector. Znaleziony element zapisujemy w nowo zadeklarowanej stałej titleList.

Następnie musimy wyczyścić zawartość tego elementu. Sposób na to znasz z poprzedniego modułu, w którym przygotowaliśmy dla Ciebie funkcję clearMessages:

function clearMessages(){
	document.getElementById('messages').innerHTML = '';
}

W tym wypadku mamy już znaleziony element, więc interesuje nas tylko ostatnia część wyrażenia w funkcji, czyli .innerHTML = '';. Zastosuj ten fragment do elementu zapisanego w stałej titleList, a jej zawartość powinna zniknąć.

Odśwież stronę, aby przetestować, czy faktycznie zniknęła zawartość listy linków w lewej kolumnie. Jeśli tak, to wiemy, że skrypt działa.

Chcielibyśmy uniknąć bałaganu w kodzie, więc teraz – kiedy skrypt już usuwa dotychczasową zawartość listy – nie będą już nam potrzebne linki zawarte w kodzie HTML. Usuń wszystkie elementy <li> wewnątrz tagu <ul class="list titles">, aby pozostała lista była początkowo pusta.

Pętla dla wszystkich artykułów

Podobną operację wykonywaliśmy już kilka razy, więc możesz wzorować się na kodzie stworzonym w poprzednim submodule. Zadeklaruj nową stałą articles i zapisz do niej odniesienie do wszystkich elementów pasujących do selektora zapisanego w stałej optArticleSelector.

Następnie wykorzystaj pętlę for-of do wykonania pozostałych operacji z osobna dla każdego z artykułów. W dalszej części submodułu założymy, że w deklaracji pętli pojedynczy artykuł został nazwany article.

Odczytanie id artykułu

Znów, nic nowego – odczytywaliśmy już atrybuty elementu. We wcześniejszym przykładzie był to atrybut href, ale sposób pobrania wartości atrybutu będzie identyczny. Zadeklaruj nową stałą o nazwie articleId i przypisz jej wartość argumentu id.

Odczytanie tytułu artykułu

To zadanie wymaga dwóch kroków — odnalezienia elementu zawierającego tytuł oraz odczytania jego zawartości. Jak już wcześniej wspomnieliśmy, do znalezienia elementu w konkretnym artykule wykorzystamy querySelector wywołany na artykule!

Jeszcze tego nie robiliśmy, więc pokażemy Ci ten fragment kodu. Aby nauczyć się na nim jak najwięcej, spróbujemy poradzić sobie z tym krokiem w jednej linii kodu!

const articleTitle = article.querySelector(optTitleSelector).innerHTML;

Przeanalizuj tę linię kodu i spróbuj na głos powiedzieć, co ona robi – czytając od lewej do prawej.

Tworzymy kod HTML linka

Mamy już wszystkie informacje, które będą nam potrzebne do stworzenia kodu linka. Co prawda wcześniej łączyliśmy już teksty, ale tym razem może to być nieco trudniejsze, ponieważ będą się pojawiać dwa rodzaje cudzysłowów:

  • pojedyncze ('), które będą rozpoczynać i kończyć ciąg znaków,
  • podwójne ("), które będą elementem kodu HTML.

Warto zacząć od zapisania kodu HTML bez żadnych stałych zawartych w nim:

const linkHTML = '<li><a href="#"><span></span></a></li>';

Podzielmy teraz ten ciąg znaków na fragmenty, zgodnie z tym, gdzie będziemy potrzebowali wstawić wartości stałych:

const linkHTML = '<li><a href="#' + '"><span>' + '</span></a></li>';

Jak wspomnieliśmy, te cudzysłowy mogą się mienić w oczach. Właśnie dlatego pokazujemy Ci, jak stworzyć tę linijkę kodu krok po kroku. Pozostaje już tylko wstawić stałe!

const linkHTML = '<li><a href="#' + articleId + '"><span>' + articleTitle + '</span></a></li>';

Dodaj console.log, aby wyświetlić kod wygenerowany za pomocą tego wyrażenia! W ten sposób możesz przyjrzeć się, czy wygenerowany kod HTML jest poprawny – np. czy poprawnie zamknęliśmy cudzysłów po wartości atrybutu href.

Dodanie linka do listy

Wszystko gotowe, można dodawać link do listy! Korzystając ze znanego Ci już innerHTML, możemy to zrobić w ten sposób:

titleList.innerHTML = titleList.innerHTML + linkHTML;

Nie jest to jednak zbyt optymalne rozwiązanie. Po każdym wykonaniu tej linii kodu zostanie dodany kolejny link, a następnie wszystkie linki zostaną na nowo wyświetlone na stronie. Wynika to z tego, że kiedy przypisujemy nową wartość do innerHTML jakiegoś elementu, jego zawartość jest usuwana i zastępowana tą zawartością.

W naszym przypadku najpierw pętla wykonuje się dla pierwszego artykułu, więc wyświetli się pierwszy link. Później pętla wykona się dla drugiego artykułu, więc do listy zostanie dodany kod HTML obu linków, zamiast jednego linka. Czyli ten pierwszy link będzie musiał zostać ponownie wyświetlony przez przeglądarkę. Ten proces będzie powtórzony tyle razy, ile mamy postów na naszym blogu.

Stanie się to tak szybko, że w ogóle tego nie zauważysz, ale na bardzo rozbudowanych stronach każde dodatkowe obciążenie jest niemile widziane.

Są dwa sposoby, które pozwalają zoptymalizować ten kod.

Wstawianie "przyległego" HTML-a

Pierwszym ze sposobów jest użycie innej metody dodawania kodu HTML do elementu. Jest to funkcja insertAdjacentHTML, którą wykonuje się na elemencie, do którego chcemy wstawić kod HTML.

Właściwie powinniśmy powiedzieć "do którego lub wokół którego", ponieważ ta funkcja pozwala na wstawienie kodu HTML przed elementem, po nim, lub wewnątrz niego (na samym początku lub końcu). Spójrz na dokumentację i przykład tej funkcji, aby lepiej zrozumieć jej działanie.

Ćwiczenie

Spróbuj samodzielnie wykorzystać tę funkcję, zamiast pokazanego powyżej sposobu z wykorzystaniem innerHTML.

Zbudowanie kodu HTML wszystkich linków

Zastosujemy ten sposób, ponieważ będzie dla nas okazją do zobaczenia w praktyce, jak działa zakres zmiennych (ang. variable scope).

Naszym celem jest stworzenie zmiennej (nie stałej!) o nazwie html, do której będziemy kolejno doklejać wszystkie linki. Wewnątrz pętli nie będziemy ich dodawać do listy. Dopiero po wykonaniu pętli wstawimy do listy kod HTML wszystkich linków naraz.

const optArticleSelector = '.post',
  optTitleSelector = '.post-title',
  optTitleListSelector = '.titles';

function generateTitleLinks(){
  /* remove contents of titleList */
  /* ... */

  /* find all the articles and save them to variable: articles */
  /* ... */

  let html = '';

  for(let article of articles){
    /* get the article id */
    /* ... */

    /* find the title element */
    /* ... */

    /* get the title from the title element */
    /* ... */

    /* create HTML of the link */
    /* ... */

    /* insert link into html variable */
    html = html + linkHTML;
  }

  titleList.innerHTML = html;
}

generateTitleLinks();

Oczywiście, nie uzupełniliśmy kodu całego skryptu. Umieściliśmy komentarz /* ... */ w miejscach, gdzie powinny znaleźć się napisane przez Ciebie fragmenty kodu (może 1 linia kodu, może więcej).

Zwróć uwagę, że zmienną html musieliśmy zdefiniować poza pętlą for-of. Inaczej ta zmienna istniałaby tylko na czas jednego "obrotu" pętli. Musieliśmy również zadbać o to, aby była to zmienna let, a nie stała const, abyśmy mogli zmieniać jej wartość.

Ćwiczenie

Na końcu pętli umieść console.log wyświetlający w konsoli zawartość zmiennej html. Odśwież stronę i zwróć uwagę, jak zawartość tej zmiennej zwiększa się z każdym "obrotem" pętli.

Teraz spróbuj przenieść wyrażenie let html = ''; do środka pętli, i ponownie odśwież stronę. Zobacz, jak zmienił się wynik wyświetlany w konsoli, oraz ile linków zostało dodanych do lewej kolumny. Czy rozumiesz, dlaczego dodał się właśnie ten konkretny link?

Zadanie: Przywrócenie funkcjonalności klikania linków

W tym submodule wygenerowaliśmy listę linków w lewej kolumnie, w oparciu o posty naszego bloga. Jeśli jednak spróbujesz teraz kliknąć któryś z tytułów, to szybko stwierdzisz, że mamy buga! Klikanie linków nie wyświetla artykułów!

U mnie działa!

Jeśli u Ciebie wszystko działa poprawnie – nie wykonujesz poleceń dokładnie. ;)

W takim wypadku przeczytaj poniższy opis buga, aby zrozumieć jego działanie i znaleźć powód, dla którego u Ciebie ten problem nie wystąpił.

Diagnozowanie buga

Ten konkretny problem często pojawia się u młodych developerów. Aby go zdiagnozować, musimy znaleźć fragment kodu, napisany w poprzednim submodule.

const links = document.querySelectorAll('.titles a');

Jest to definicja linków, którym chwilę później dodajemy listener nasłuchujący eventu click i wywołujący funkcję clickHandler.

Pod linią kodu, którą zacytowaliśmy powyżej, dodaj console.log, sprawdzający co zawiera stała links. W zależności od tego, jak dokładnie wykonujesz polecenia z tego submodułu, możesz zobaczyć w konsoli:

  • pustą kolekcję elementów, czyli nie znaleziono żadnego linka,
  • kolekcję linków.

W drugim przypadku rozwiń tę kolekcję i kliknij prawym przyciskiem myszy w któryś z linków. Spróbuj odnaleźć go w panelu za pomocą opcji Reveal in elements panel. Jak szybko zauważysz, nie da się znaleźć tego linka.

Te dwa przypadki różnią się tylko tym, czy w Twoim kodzie HTML zostały usunięte linki z listy w lewej kolumnie (pierwszy przypadek), czy też zostały w kodzie HTML (drugi przypadek).

W obu przypadkach wynika to z faktu, że ten skrypt (włącznie z nowo-dodanym console.log) wykonał się, zanim wygenerowaliśmy listę linków!

Popatrz na zawartość swojego pliku js/script.js – najpierw znajdujemy linki i dodajemy do nich event listenery, a dopiero potem czyścimy listę linków i generujemy nowe linki!

Rozwiązanie buga

Zamiana kolejności mogłaby pomóc, ale tylko na tym etapie prac. Jak wiesz, zamierzamy generować nową listę linków np. po kliknięciu tagu, więc ten problem by się powtórzył.

Dlatego kod odpowiedzialny za powiązanie kliknięcia w linki z funkcją titleClickHandler musimy przenieść na sam koniec funkcji generateTitleLinks. Mówimy tutaj o deklaracji stałej links oraz pętli for-of, w której dodajemy event listenery do tych linków. Nie musisz przenosić funkcji titleClickHandler.

Teraz klikanie w linki powinno działać tak jak wcześniej – ale klikamy już w linki wygenerowane za pomocą JS!

6.5. Podsumowanie

Po wykonaniu wszystkich zadań w tym module strona po odświeżeniu wygląda dokładnie tak samo, jak wcześniej – ale to nie oznacza, że nic się nie zmieniło! Dzięki naszej pracy możemy przełączać się pomiędzy artykułami! Przygotowaliśmy też generowanie listy linków do artykułów, które już niedługo wykorzystamy do wyświetlania przefiltrowanej listy artykułów!

Jednocześnie udało nam się wiele nauczyć:

  • o zmiennych i stałych oraz ich zakresach,
  • o eventach, listenerach i handlerach,
  • o pętlach i iteracji po elementach kolekcji,
  • o znajdowaniu elementów na stronie i zmianie ich zawartości,
  • o generowaniu i wstawianiu kodu HTML nowych elementów,
  • o pobieraniu atrybutów i zmianach klas elementów na stronie,
  • i wreszcie, o zapisywaniu algorytmu przed rozpoczęciem pisania skryptu.

Ten ostatni punkt jest kluczowy. To on scala wszystkie pozostałe elementy w działający skrypt, który pozwala nam rozbudowywać nasz projekt.

W następnym module będziemy dalej pracować nad tym projektem. Jak pamiętasz ze specyfikacji, pozostało nam dodanie tagów i autorów dla każdego artykułu, generowanie linków w prawej kolumnie, oraz filtrowanie linków do artykułów.

Dla chętnych

Jeżeli udało Ci się wykonać wszystkie zadania w tym module przed czasem – gratulacje! Sugerujemy, by wykorzystać ten moment na poćwiczenie umiejętności z poprzednich modułów. Poniżej znajdziesz kilka opcjonalnych zadań, dzięki którym odświeżysz swoją wiedzę.

Stylowanie strony

Puść wodze fantazji i ostyluj projekt zgodnie z własnym gustem. Pamiętaj o dobrych praktykach (czytelność, estetyka, spójne kolory) i spraw, by strona prezentowała się dobrze zarówno w wersji desktop, jak i mobile.

Animacja przełączania artykułów

Artykuły na Twoim blogu pojawiają się po kliknięciu na tytuł, ale nie wygląda to zbyt estetycznie. Spróbuj sprawić, by artykuły pojawiały się płynniej: zamiast przełączania właściwości display (z none na block i na odwrót), pobaw się z opacity i transition. Możesz też potrzebować position: absolute; by znikające artykuły nie zostawiały po sobie pustego miejsca na stronie.

Dodatkowo aktywny artykuł powinien mieć wyższy z-index niż pozostałe, aby dało się w nim zaznaczyć fragment tekstu (np. gdyby ktoś chciał kliknąć w link). Wynika to z faktu, że jeśli wszystkie artykuły będą miały display: block;, to te z opacity: 0; mogą uniemożliwiać kliknięcie w link aktywnym artykule.

Rozbudowa task runnera

Teraz kiedy piszemy już własne skrypty, warto zainstalować kolejny pakiet NPM, który sprawdzi za nas kod JS. Twoim zadaniem jest instalacja w tym projekcie paczki Eslint i dodanie do task runnera kolejnego taska typu test: (podobnego do tego, który sprawdza poprawność HTML), który odpali sprawdzanie pliku script.js. Spróbuj zrobić to samodzielnie, opierając się na dokumentacji – w następnym module podamy rozwiązanie, więc będziesz mieć szansę sprawdzić swoją wersję.

6.6. Quiz powtórkowy

Na koniec tego modułu przygotowaliśmy dla Ciebie quiz powtórkowy. Pomoże Ci on powtórzyć wiedzę z poprzednich modułów.

Odpowiedzi tego quizu nie są nigdzie zapisywane, więc są tylko do Twojej wiadomości. Ten quiz ma Ci posłużyć jako pomoc w nauce – dlatego pod każdym pytaniem znajdziesz guzik, który sprawdzi poprawność Twoich odpowiedzi oraz poda Ci wyjaśnienie zagadnienia poruszanego w tym pytaniu.

1. Zaznacz prawdziwe zdania dotyczące if/else:

Wyjaśnienie

W if/else musi być jeden blok if. Opcjonalnie, może zawierać dowolną liczbę bloków else if oraz jeden blok else.

Najczęstszą pomyłką przy korzystaniu z if/else jest użycie pojedynczego znaku =. Jest to operator przypisania i służy do przypisania wartości do zmiennej.

Sprawdzenie, czy dwie wartości są takie same, wykonujemy za pomocą operatorów == i ===. Różnią się one tym, że pierwszy automatycznie konwertuje wartości do innych typów, czyli np. prawdą będzie, że 3 == '3'. W przypadku === to wyrażenie będzie fałszywe, ponieważ jedna z wartości jest liczbą, a druga tekstem.

2. Zaznacz prawdziwe zdania dotyczące funkcji:

Wyjaśnienie

Funkcja jest fragmentem kodu, który może być wykonywany wielokrotnie. Może przyjmować argumenty i zwracać jakąś wartość.

3. Zaznacz prawdziwe zdania dotyczące eventów:

Wyjaśnienie

Event to zdarzenie, np. kliknięcie, które wykonuje się najpierw na jakimś (np. klikniętym) elemencie, potem na jego rodzicu itd. – w ten sposób event jest propagowany aż do body, document i window. Możemy jednak zatrzymać propagację, dodając do jakiegoś elementu listener i używając w nim event.stopPropagation().

Event listener pozwala nam na nasłuchiwanie konkretnego rodzaju eventu na wybranym przez nas elemencie, niezależnie od tego, czy jest to element, na którym pierwotnie został wykonany event, czy event dotarł do tego elementu poprzez propagację.

Domyślne akcje eventów, np. kliknięcia w link, można zablokować. Służy do tego funkcja event.preventDefault().

4. Zakres zmiennych:

Wyjaśnienie

Zakres zmiennych mówi o tym, że zmienna istnieje tylko w bloku kodu (np. if, funkcja, etc.), bezpośrednio w której została zdefiniowana. Oznacza to, że jeśli chcemy np. zliczać ilość wywołań jakiejś funkcji, to zmienną w której przechowujemy liczbę wywołań, musimy zadeklarować poza tą funkcją.

5. Aby w JSie móc wykorzystywać takie same selektory, jak w CSS-ie, musimy:

Wyjaśnienie

W JavaScripcie możemy znajdować elementy DOM za pomocą selektorów CSS-owych tylko w funkcjach, które jako argument przyjmują selektor. Najczęściej wykorzystywanym przykładem może być funkcja querySelector.

;